package com.simon.train.generator.gen;

import com.simon.train.generator.util.DbUtil;
import com.simon.train.generator.util.Field;
import com.simon.train.generator.util.FreemarkerUtil;
import freemarker.template.TemplateException;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.IOException;
import java.util.*;

public class ServerGenerator {

    /**
     * 前端生成的页面是否是只读的
     * true则只生成查 false则生成增删改查四个
     */
    static boolean readOnly = true;

//    static String vuePath = "web/src/views/main/";

    static String vuePath = "admin/src/views/main/";

    static String serverPath = "[module]/src/main/java/com/simon/train/[module]/";

    static String pomPath = "generator/pom.xml";

    static String module = "";

    static String Domain = "";

    static String domain = "";

    /**
     * 前端vue文件的命名
     * 我定义的是类似passenger + View的名字 我喜欢这种
     */
    static String do_main = "";

    /**
     * 参数映射表
     */
    static Map<String, Object> param = new HashMap<>();

//    static {
//        new File(servicePath).mkdirs();
//    }

    public static void main(String[] args) throws Exception {
        /**
         * 配置文件pom.xml中configurationFile的路径 也就是目标xml的路径位置
         */
        String generatorPath = getGeneratorPath();

        // 比如generator-config-member.xml，得到module = member
        // 将src/main/resources/generator-config-替换为空，将.xml也替换为空
        module = generatorPath.replace("src/main/resources/generator-config-", "").replace(".xml", "");
        System.out.println("module: " + module);

        //统一替换模块名module
        serverPath = serverPath.replace("[module]", module);
        new File(serverPath).mkdirs();
        System.out.println("servicePath: " + serverPath);


        /**
         * 进一步读取下一个的目标xml配置文件
         * 同时读取完毕后将类名和参数等属性组装到类的静态方法中
         */
        GetNextXMLAndSetValue(generatorPath);


        /**
         * 针对调用生成器的部分进行统一的封装
         */
        generate(Domain, param, "service","service");
//        generate(Domain, param, "controller","controller"); //普通的controller生成器 用controller.ftl
        generate(Domain, param, "controller/admin","adminController"); //管理端的controller生成，用adminController.ftl
        generate(Domain, param, "dto","saveDto");
        generate(Domain, param, "dto", "queryDto");
        generate(Domain, param, "vo", "queryVO");

        /**
         * 前端代码生成器的调用部分
         */
//        genVue("vue.ftl", do_main, param);
        genVue("vueAdmin.ftl", do_main, param);

    }

    /**
     * 前端页面生成器
     * @param do_main 前端文件命名风格 原作者是java-demo.vue这种 我还是喜欢JavaDemo.vue这种
     *                该变量的名字就是最终在相应路径下的vue文件名
     *                我定义的就是passengerView.vue这种
     * @param param
     * @throws IOException
     * @throws TemplateException
     */
    private static void genVue(String FTLFileName, String do_main, Map<String, Object> param) throws IOException, TemplateException {
        FreemarkerUtil.initConfig(FTLFileName);
        new File(vuePath + module).mkdirs();
        String fileName = vuePath + module + "/" + do_main + ".vue";
        System.out.println("开始生成：" + fileName);
        FreemarkerUtil.generator(fileName, param);
    }

    /**
     * 进一步进行封装 进一步读取下一个的目标xml配置文件
     * 同时读取完毕后将类名和参数等属性组装到类的静态方法中
     * @param generatorPath
     * @return
     * @throws DocumentException
     */
    private static void GetNextXMLAndSetValue(String generatorPath) throws Exception {
        /**
         * 在刚才的配置文件路径的基础上 继续去读目标的xml文件
         * 并且找到table节点和两个属性
         */
        Document document = new SAXReader().read("generator/" + generatorPath);
        Node table = document.selectSingleNode("//table");
        System.out.println(table);
        Node tableName = table.selectSingleNode("@tableName");
        Node domainObjectName = table.selectSingleNode("@domainObjectName");
        System.out.println(tableName.getText() + "/" + domainObjectName.getText()); //passenger/Passenger

        // 示例：表名 jiawa_test
        // Domain = JiawaTest
        Domain = domainObjectName.getText();

        // domain = jiawaTest
        //将开头的大写字母变成小写
        domain = Domain.substring(0, 1).toLowerCase() + Domain.substring(1);

        // do_main
//        do_main = tableName.getText().replaceAll("_", "-");
//        do_main = tableName.getText() + "View";
        do_main = domain;

        /**
         * 为了减少代码的冗余程度 把为DBUtil的三个数据库字段赋值的操作封装到函数里
         * 必须先设置值以后 后续调用get等才不会出错
         */
        SetValueForDBUtil(document);

        // 获取表中文名和所有的列信息
        String tableNameCn = DbUtil.getTableComment(tableName.getText());
        List<Field> fieldList = DbUtil.getColumnByTableName(tableName.getText());
        Set<String> typeSet = getJavaTypes(fieldList);

        // 组装参数
        param.put("module",module);
        param.put("Domain", Domain);
        param.put("readOnly",readOnly);
        param.put("domain", domain);
        /**
         * do_main这个参数我不喜欢用 我喜欢url请求的地址还是用domain
         */
        param.put("do_main", do_main);
        param.put("tableNameCn", tableNameCn); //表的注释信息 comment字段的数据
        param.put("fieldList", fieldList);  //所有的表列名
        param.put("typeSet", typeSet);  //所有数据类型对应的无重复set
        System.out.println("组装参数：" + param);
    }

    /**
     * 为DbUtil设置数据源
     * @param document
     */
    private static void SetValueForDBUtil(Document document) {
        Node connectionURL = document.selectSingleNode("//@connectionURL");
        Node userId = document.selectSingleNode("//@userId");
        Node password = document.selectSingleNode("//@password");
        System.out.println("url: " + connectionURL.getText());
        System.out.println("user: " + userId.getText());
        System.out.println("password: " + password.getText());
        DbUtil.url = connectionURL.getText();
        DbUtil.user = userId.getText();
        DbUtil.password = password.getText();
    }

    /**
     * 获取所有的Java类型，使用Set去重
     * 比如最后可以获得一个无重复的set集合 里面有Long,String,Date这种
     * 便于我们记录一个实体类对应的属性有几种数据类型 从而便于生成器更好的生成
     */
    private static Set<String> getJavaTypes(List<Field> fieldList) {
        Set<String> set = new HashSet<>();
        for (int i = 0; i < fieldList.size(); i++) {
            Field field = fieldList.get(i);
            set.add(field.getJavaType());
        }
        return set;
    }

    /**
     * 代码生成器
     * @param Domain 从配置文件读出来的当前的实体的类名 比如Passenger或者Member
     * @param param 参数的对应Map
     * @param packageName 包名 比如是dto 或者是service controller这种
     * @param target 目标的ftl模板文件的名字 比如saveDto.ftl 则传入saveDto
     *               target后续还会把首字母大写变成Target 比如SaveDto 然后这个一起拼接到完整类名
     *               路径toPath(.../dto/) + Domain(Passenger) + Target(SaveDto) + .java
     * @throws IOException
     * @throws TemplateException
     */
    private static void generate(String Domain, Map<String, Object> param, String packageName, String target) throws IOException, TemplateException {
        FreemarkerUtil.initConfig(target + ".ftl");
        String toPath = serverPath + packageName + "/";
        new File(toPath).mkdirs();
        String Target = target.substring(0, 1).toUpperCase() + target.substring(1);
        String fileName = toPath + Domain + Target + ".java";
        System.out.println("开始生成：" + fileName);
        FreemarkerUtil.generator(fileName, param);
    }

    /**
     * 读取pom.xml中configurationFile的路径的方法
     * 也就是读取xml文件的代码
     * 常用则直接抽象出来
     * e.g. src/main/resources/generator-config-member.xml
     * @throws DocumentException
     */
    private static String getGeneratorPath() throws DocumentException {
        SAXReader saxReader = new SAXReader();
        Map<String, String> map = new HashMap<String, String>();
        map.put("pom", "http://maven.apache.org/POM/4.0.0");
        saxReader.getDocumentFactory().setXPathNamespaceURIs(map);
        Document document = saxReader.read(pomPath);
        //XPath定位节点 pom是xml命名空间 如果要找属性则用@xxx
        Node node = document.selectSingleNode("//pom:configurationFile");
        System.out.println(node.getText());
        return node.getText();
    }

}
